/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.annotation.SorterOrder;
import cn.star.framework.core.IEntity;
import cn.star.framework.core.IService;
import cn.star.framework.core.ITreeEntity;
import cn.star.framework.core.api.AppResult;
import cn.star.framework.core.api.Pageable;
import cn.star.framework.entity.AbstractBaseEntity;
import cn.star.framework.excel.convert.ValueWrapper;
import cn.star.framework.excel.convert.ValueWrapperConverter;
import cn.star.framework.excel.core.Column;
import cn.star.framework.excel.core.Type;
import cn.star.framework.excel.enhance.ClassReadContext;
import cn.star.framework.excel.enhance.ClassWriteContext;
import cn.star.framework.excel.enhance.ExcelHelper;
import cn.star.framework.excel.enhance.GlobalConverterRegister;
import cn.star.framework.excel.read.ReadContext;
import cn.star.framework.excel.read.ReadContextHolder;
import cn.star.framework.excel.read.SimpleAnalysisEventListener;
import cn.star.framework.excel.write.WriteContext;
import cn.star.framework.read.SaveOrUpdateReadRowCallback;
import cn.star.framework.sql.Identifier;
import cn.star.framework.sql.standard.Sorter;
import cn.star.framework.sql.standard.Where;
import cn.star.framework.sql.standard.WhereGroup;
import cn.star.framework.support.HttpServletRequestQueryBuilder;
import cn.star.framework.type.DataTreeType;
import cn.star.framework.type.OrderType;
import cn.star.framework.type.QueryType;
import cn.star.framework.util.SpringContextUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

/**
 * Mvc抽象层<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
public abstract class AbstractController<T extends IEntity, Id extends Serializable> {
  @Deprecated
  protected HttpServletRequestQueryBuilder getQueryBuilder() {
    HttpServletRequest request =
        ((ServletRequestAttributes)
                Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
            .getRequest();

    return this.getQueryBuilder(request);
  }

  protected HttpServletRequestQueryBuilder getQueryBuilder(HttpServletRequest request) {
    return this.getQueryBuilder(request, null);
  }

  protected HttpServletRequestQueryBuilder getQueryBuilder(
      HttpServletRequest request, Class<T> type) {
    return this.wrapper(new HttpServletRequestQueryBuilder(request, type), type);
  }

  /**
   * 包装默认的排序
   *
   * @param queryBuilder {@link HttpServletRequestQueryBuilder}
   * @param type 实体类型
   */
  @SuppressWarnings("all")
  protected HttpServletRequestQueryBuilder wrapper(
      HttpServletRequestQueryBuilder queryBuilder, Class<T> type) {
    List<Sorter> list = queryBuilder.getSorters();
    if (type != null && CollectionUtil.isEmpty(list)) {
      Field[] fields = ReflectUtil.getFields(type);
      for (Field field : fields) {
        SorterOrder order = field.getAnnotation(SorterOrder.class);
        if (order != null) {
          Sorter sorter = new Sorter();
          OrderType orderType = order.type();
          sorter.setType(orderType);
          sorter.addNames(new Identifier(type, field));

          list.add(sorter);
        }
      }
      queryBuilder.setSorters(list);
    }

    return queryBuilder;
  }

  /**
   * 获取{@link IService}
   *
   * @return {@link IService}
   */
  protected abstract IService<T, Id> getService();

  protected <DataType> AppResult<DataType> ofSuccess() {
    return AppResult.ofSuccess();
  }

  protected <DataType> AppResult<DataType> ofSuccess(DataType data) {
    return AppResult.ofSuccess(data);
  }

  protected AppResult<String> ofSuccessMessage(String message) {
    return AppResult.ofSuccessMessage(message);
  }

  protected AppResult<String> ofFail(String message) {
    return AppResult.ofFail(message);
  }

  /**
   * {@link PostMapping}
   *
   * @param entity {@link T} - {@link ResponseBody}
   * @return {@link AppResult}
   */
  public AppResult<T> save(T entity) {
    return this.ofSuccess(this.getService().save(entity));
  }

  @SuppressWarnings("all")
  protected void checkBeforeUpdate(T entity) {
    if (entity != null) {
      Field[] fields = ReflectUtil.getFields(entity.getClass());
      for (Field field : fields) {
        if (field.getAnnotation(javax.persistence.Id.class) != null) {
          Object value = ReflectUtil.getFieldValue(entity, field);
          Assert.isFalse(
              value == null, "primary key {} empty, update check failure", field.getName());
        }
      }

      if (entity instanceof AbstractBaseEntity) {
        Assert.isFalse(
            ((AbstractBaseEntity) entity).getVersion() == null,
            "version empty, update check failure");
      }
    }
  }

  /**
   * {@link PutMapping}
   *
   * @param entity {@link T} - {@link ResponseBody}
   * @return {@link AppResult}
   */
  public AppResult<T> update(T entity) {
    this.checkBeforeUpdate(entity);
    return this.save(entity);
  }

  /**
   * {@link DeleteMapping}
   *
   * @param ids {@link Id} - {@link ResponseBody}
   * @return {@link AppResult}
   */
  public AppResult<List<T>> deleteByIds(Id... ids) {
    List<T> data = this.getService().deleteByIds(ids);
    return this.ofSuccess(data);
  }

  /**
   * {@link GetMapping}
   *
   * @param request {@link HttpServletRequest}
   * @param type 实体类型
   * @return {@link AppResult}
   */
  public AppResult<List<T>> list(HttpServletRequest request, Class<T> type) {
    HttpServletRequestQueryBuilderTuple x = this.wrapper(request, type);
    HttpServletRequestQueryBuilder queryBuilder = x.getQueryBuilder();

    List<T> list = this.getService().findAll(queryBuilder);

    if (!x.isQueryTree()) {
      this.wrapperTree(list, queryBuilder.getDataTreeType());
    }

    return this.ofSuccess(list);
  }

  /**
   * {@link GetMapping}/id
   *
   * @param id {@link Id} - {@link PathVariable}
   * @return {@link AppResult}
   */
  public AppResult<T> get(Id id) {
    T entity = this.getService().findById(id);
    return this.ofSuccess(entity);
  }

  /**
   * 查询树叶子节点数据
   *
   * @param type 实体类型
   * @param id {@link ITreeEntity#getParentId()}
   * @return 叶子节点集合，若不是树而获取叶子节点则抛出异常
   */
  public AppResult<List<T>> getChildren(Class<T> type, String id) {
    Assert.isFalse(
        this.isNotTree(type),
        "The current type {} does not implement {}, unable to obtain children",
        type.getName(),
        ITreeEntity.class.getName());
    List<T> list =
        this.getService().findAll((root, query, cb) -> cb.equal(root.get("parentId"), id));

    this.wrapperTree(list, DataTreeType.ASYNC);

    return this.ofSuccess(list);
  }

  /**
   * 判断是否为树
   *
   * @param type 实体类型
   */
  protected boolean isTree(Class<?> type) {
    return ITreeEntity.class.isAssignableFrom(type);
  }

  /**
   * 判断是否为树
   *
   * @param type 实体类型
   */
  protected boolean isNotTree(Class<?> type) {
    return !this.isTree(type);
  }

  /**
   * 判断是否有条件搜索<br>
   * 对于树结构来说，若无条件搜索返回树结构数据；否则进行所有检索
   *
   * @param queryBuilder {@link HttpServletRequestQueryBuilder}
   * @param type 实体类型
   */
  protected boolean isQueryTree(HttpServletRequestQueryBuilder queryBuilder, Class<?> type) {
    return this.isTree(type) && CollectionUtil.isNotEmpty(queryBuilder.getWhereGroups());
  }

  private static class HttpServletRequestQueryBuilderTuple {
    @Getter private final HttpServletRequestQueryBuilder queryBuilder;
    @Getter private final boolean queryTree;

    public HttpServletRequestQueryBuilderTuple(
        HttpServletRequestQueryBuilder queryBuilder, boolean queryTree) {
      this.queryBuilder = queryBuilder;
      this.queryTree = queryTree;
    }
  }

  private HttpServletRequestQueryBuilderTuple wrapper(HttpServletRequest request, Class<T> type) {
    HttpServletRequestQueryBuilder queryBuilder = this.getQueryBuilder(request, type);
    boolean isTree = this.isTree(type), isQueryTree = this.isQueryTree(queryBuilder, type);
    HttpServletRequestQueryBuilderTuple x =
        new HttpServletRequestQueryBuilderTuple(queryBuilder, isQueryTree);
    if (isTree && !isQueryTree) {
      WhereGroup whereGroup = new WhereGroup();
      whereGroup.getWheres().add(new Where(new Identifier("parentId"), QueryType.NULL));

      x.getQueryBuilder().addWhereGroups(whereGroup);
    }

    return x;
  }

  /**
   * {@link GetMapping}/page
   *
   * @param request {@link HttpServletRequest}
   * @param type 实体类型
   * @return {@link AppResult} {@link Pageable}
   */
  public AppResult<Pageable<T>> pageable(HttpServletRequest request, Class<T> type) {
    HttpServletRequestQueryBuilderTuple x = this.wrapper(request, type);
    HttpServletRequestQueryBuilder queryBuilder = x.getQueryBuilder();

    Pageable<T> pageable =
        this.getService()
            .pageable(queryBuilder, queryBuilder.getPageNo(), queryBuilder.getPageSize());

    if (!x.isQueryTree()) {
      this.wrapperTree(pageable.getData(), queryBuilder.getDataTreeType());
    }

    return this.ofSuccess(pageable);
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  protected void wrapperTree(List<T> data, DataTreeType dataTreeType) {
    if (CollectionUtil.isNotEmpty(data)) {
      if (data.get(0) instanceof ITreeEntity) {
        for (T item : data) {
          ITreeEntity x = (ITreeEntity) item;
          List<T> children =
              this.getService()
                  .findAll((root, query, cb) -> cb.equal(root.get("parentId"), x.getId()));
          if (CollectionUtil.isNotEmpty(children)) {
            if (dataTreeType == DataTreeType.ASYNC) {
              x.setChildren(CollectionUtil.newArrayList());
            } else if (dataTreeType == DataTreeType.SYNC) {
              this.wrapperTree(children, dataTreeType);
              x.setChildren(children);
            }
          }
        }
      }
    }
  }

  @SuppressWarnings("all")
  public AppResult<Id> imp(String tmp, MultipartFile file, Class<T> type) {
    ServletRequestAttributes attributes =
        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();

    return this.imp(tmp, request, file, type);
  }

  /**
   * 数据导入
   *
   * @param tmp 文件临时目录
   * @param file 数据文件
   * @param type 对应实体类型
   * @return {@link AppResult}
   * @throws IOException
   */
  @SneakyThrows
  @SuppressWarnings("all")
  public AppResult<Id> imp(
      String tmp, HttpServletRequest request, MultipartFile file, Class<T> type) {
    Id id = (Id) IdUtil.fastSimpleUUID();
    String originName = file.getOriginalFilename();
    File dir = new File(tmp);
    if (!dir.exists()) {
      dir.mkdirs();
    }
    File excel = new File(tmp + File.separator + id + originName);
    file.transferTo(excel);
    int sheetCount = ExcelHelper.INSTANCE.getSheetCount(excel);
    ReadContext context = new ClassReadContext(type);
    context.setFile(excel);
    context.setSheetCount(sheetCount);

    ReadContextHolder.put(id.toString(), context);

    HttpServletRequestQueryBuilder queryBuilder = this.getQueryBuilder(request, type);
    queryBuilder.getIgnoreReadeColumns().forEach(name -> context.addExcludeColumns(name));

    // 使用新的线程进行异步处理
    SpringContextUtil.getBean(this.getClass()).read(excel, context, type);

    return this.ofSuccess(id);
  }

  @Async
  public void read(File excel, ReadContext context, Class<T> type) {
    IService<T, Id> service = this.getService();
    Assert.isFalse(service == null, "service is empty");
    long start = System.currentTimeMillis();
    log.info("read data type {} for excel {}", type.getSimpleName(), excel.getName());
    SimpleAnalysisEventListener listener =
        new SimpleAnalysisEventListener(
            context, new SaveOrUpdateReadRowCallback<T, Id>(service, type));
    EasyExcel.read(excel, listener).doReadAll();
    log.info("read data success, duration {} ms", System.currentTimeMillis() - start);
  }

  protected boolean isEmpty(Pageable<T> pageable) {
    return pageable == null || CollectionUtil.isEmpty(pageable.getData());
  }

  protected boolean isNotEmpty(Pageable<T> pageable) {
    return pageable != null && CollectionUtil.isNotEmpty(pageable.getData());
  }

  @SneakyThrows
  @SuppressWarnings("all")
  public void write(
      HttpServletRequest request,
      HttpServletResponse response,
      Class<T> type,
      String fileName,
      String sheetName) {
    String file = StrUtil.isNotBlank(fileName) ? fileName : type.getSimpleName();
    String sheet = StrUtil.isNotBlank(sheetName) ? sheetName : type.getSimpleName();

    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");
    response.setHeader("Content-disposition", "attachment;filename=" + file + ".xlsx");

    // 注册转换函数
    GlobalConverterRegister.register(Type.WRITE, ValueWrapperConverter.INSTANCE);

    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();

    HttpServletRequestQueryBuilder queryBuilder = this.getQueryBuilder(request, type);
    WriteContext context = new ClassWriteContext(sheet, type);
    queryBuilder.getIgnoreWriteColumns().forEach(name -> context.addExcludeColumns(name));

    Map<Integer, Column> header = context.getHeaderMap();

    WriteSheet writeSheet = context.builder();
    int pageNo = queryBuilder.getPageNo(), pageSize = queryBuilder.getPageSize();

    while (true) {
      Pageable<T> pageable = this.getService().pageable(queryBuilder, pageNo, pageSize);
      if (this.isNotEmpty(pageable)) {
        List<Map<Integer, ValueWrapper<?>>> list = ValueWrapper.wrapper(header, pageable.getData());
        excelWriter.write(list, writeSheet);
        pageNo++;
      } else {
        break;
      }
    }

    if (pageNo == queryBuilder.getPageNo()) {
      // 写一条空数据，导出表头
      excelWriter.write(
          ValueWrapper.wrapper(header, Collections.singletonList(ReflectUtil.newInstance(type))),
          writeSheet);
    }

    excelWriter.finish();
  }

  /**
   * 数据导出
   *
   * @param request {@link HttpServletRequest}
   * @param response {@link HttpServletResponse}
   * @param type 实体类型
   */
  public void exp(HttpServletRequest request, HttpServletResponse response, Class<T> type) {
    this.write(request, response, type, type.getSimpleName(), type.getSimpleName());
  }
}
